use pin_project_lite::pin_project;
use std::{
    future::Future,
    pin::Pin,
    task::{Context, Poll},
};

pub trait Moment: Future<Output = ()> {}

pin_project!(
    /// 包装一个不返回的future，在ready后再`.await`将立刻返回
    #[derive(Debug)]
    #[must_use = "futures do nothing unless you `.await` or poll them"]
    pub struct Until<F> {
        #[pin]
        inner: Option<F>,
    }
);

impl<F> Until<F> {
    pub fn new(inner: F) -> Self {
        Self { inner: Some(inner) }
    }
}

impl<F: Future<Output = ()>> Future for Until<F> {
    type Output = ();

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        match self.as_mut().project().inner.as_pin_mut() {
            None => Poll::Ready(()),
            Some(inner) => {
                let poll = inner.poll(cx);
                if let Poll::Ready(()) = poll {
                    self.project().inner.set(None);
                }
                poll
            }
        }
    }
}

impl<F: Future<Output = ()>> Moment for Until<F> {}

#[cfg(test)]
mod test {
    use crate::moment::Until;
    use std::{cell::Cell, pin::pin};

    #[test]
    fn test() {
        smol::block_on(async {
            let x = Cell::new(0);

            let mut fut = pin!(Until::new(async {
                x.set(x.get() + 1);
            }));

            assert_eq!(x.get(), 0);
            (&mut fut).await;
            assert_eq!(x.get(), 1);
            (&mut fut).await;
            assert_eq!(x.get(), 1);
        });
    }
}
